package com.scs.application.core.service;

import cn.afterturn.easypoi.excel.entity.result.ExcelImportResult;
import cn.afterturn.easypoi.exception.excel.ExcelImportException;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.enums.SqlMethod;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
import com.scs.application.consts.GlobalConsts;
import com.scs.application.core.aliyun.oss.handler.AliyunOssHandler;
import com.scs.application.core.aliyun.oss.properties.AliyunOssProperties;
import com.scs.application.core.dto.ExcelImportField;
import com.scs.application.core.exceptions.BusinessException;
import com.scs.application.core.exceptions.ImportException;
import com.scs.application.core.export.ExportParamDto;
import com.scs.application.core.export.ExportService;
import com.scs.application.core.model.DataEntity;
import com.scs.application.core.model.ImportResult;
import com.scs.application.core.msg.provider.ISceneMsgPushProvider;
import com.scs.application.core.utils.*;
import com.scs.application.modules.design.service.QueryGenerator;
import com.scs.application.modules.ext.entity.File;
import com.scs.application.modules.ext.service.FileService;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.poi.hssf.usermodel.*;
import org.apache.poi.ss.usermodel.Font;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.VerticalAlignment;
import org.apache.poi.ss.util.CellRangeAddress;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.StopWatch;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Validator;
import java.io.*;
import java.util.*;
import java.util.concurrent.ForkJoinPool;

/**
 */
@Slf4j
public class BaseServiceImpl<M extends BaseMapper<T>, T> extends ServiceImpl<M, T> implements IBaseService<T> {

    @Autowired
    protected Validator validator;

    @Autowired
    protected ForkJoinPool forkJoinPool;

    @Value("${res.real-path}")
    protected String resPath;


    @Autowired(required = false)
    private AliyunOssProperties aliyunOssProperties;

    @Autowired(required = false)
    private AliyunOssHandler aliyunOssHandler;

    @Autowired(required = false)
    private ISceneMsgPushProvider sceneMsgPushProvider;

    @Autowired
    private SqlSessionFactory sqlSessionFactory;

    @Autowired
    public ExportService exportService;

    @Autowired
    @Lazy
    FileService fileService;


    @Transactional
    @Override
    public ImportResult excelImport(MultipartFile file) {
        return excelImport(file, null);
    }

    @Transactional
    @Override
    public ImportResult excelImport(MultipartFile multipartFile, Map<String, Object> params) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();

        ExcelImportResult result = null;
        Exception ex = null;
        try {
            beforeImport();
            result = doExcelImport(multipartFile, params);
            stopWatch.stop();
            return ImportResult.build(stopWatch.getTotalTimeSeconds(), result);
        }catch (ExcelImportException e){
            log.error("数据导入失败", e);
            ex = e;
            throw e;
        }catch (BusinessException e) {
            log.error("数据导入失败", e);
            ex = e;
            throw e;
        } catch (Exception e) {
            log.error("数据导入失败", e);
            ex = e;
            throw new ImportException("导入失败", e);
        } finally {
            afterImport(result, multipartFile, ex);
        }
    }

    @Override
    public <R> boolean saveBatch(Class<R> modelClass, Collection<R> entityList, int batchSize) {
        String sqlStatement = SqlHelper.table(modelClass).getSqlStatement(SqlMethod.INSERT_ONE.getMethod());

        try (SqlSession batchSqlSession = sqlSessionBatch()) {
            int i = 0;
            for (R anEntityList : entityList) {
                batchSqlSession.insert(sqlStatement, anEntityList);
                if (i >= 1 && i % batchSize == 0) {
                    batchSqlSession.flushStatements();
                }
                i++;
            }
            batchSqlSession.flushStatements();
        }
        return true;
    }

    protected void beforeImport() {
        log.info("===============================================批量导入开始=================================================");
    }

    protected ExcelImportResult doExcelImport(MultipartFile multipartFile, Map<String, Object> params) throws Exception {
        throw new UnsupportedOperationException("不支持的EXCEL导入操作");
    }


    protected void afterImport(ExcelImportResult result, MultipartFile multipartFile, Exception e) {
        log.info("===============================================批量导入结束=================================================");
        if(result == null){
            return;
        }

        ByteArrayOutputStream bos = null;
        try {
            bos = new ByteArrayOutputStream();

            if (result.getFailWorkbook() != null) {
                result.getFailWorkbook().write(bos);
            }


            if (aliyunOssHandler != null && sceneMsgPushProvider != null) {
                String loginKey = StringUtils.defaultIfBlank(UserUtils.currentUser().getLoginKey(), "default");
                String fileName = "excel/fail-result/" + loginKey + "/" + multipartFile.getOriginalFilename();
                ByteArrayOutputStream finalBos = bos;
//                this.forkJoinPool.execute(() -> {
//
//
//                    aliyunOssHandler.execute((ossClient) -> {
//                        PutObjectResult putObjectResult = ossClient.putObject(aliyunOssProperties.getBucketName(), fileName, new ByteArrayInputStream(finalBos.toByteArray()));
//                        Date expiration = new Date(System.currentTimeMillis() + 3600 * 1000);
//                        String signUrl = aliyunOssHandler.getSignUrl(fileName, expiration);
//
//                        SceneMessage sceneMessage = new SceneMessage();
//                        sceneMessage.setSceneCode(MsgConsts.SCENE_IMPORT_FAILED_NOTICE)
//                                .setBizType("import_failed").setBizKey("")
//                                .setReceiver(loginKey).setReceiverType(ReceiverType.USER)
//                                .addParam("importTime", DateUtils.format(new Date(), DateUtils.DEFAULT_PATTER), MessageType.INNER)
//                                .addParam("downloadUrl", signUrl, MessageType.INNER)
//                                .addParam("expiredTime", DateUtils.format(expiration, DateUtils.DEFAULT_PATTER), MessageType.INNER)
//                                .addParam("creator", loginKey, MessageType.INNER);
//
//                        sceneMsgPushProvider.push(sceneMessage);
//                        log.info("本次导入失败数据已上传至OSS，地址：{}", signUrl);
//                        return putObjectResult;
//                    });
//                });
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            if (bos != null) {
                try {
                    bos.close();
                } catch (IOException ex) {
                    ex.printStackTrace();
                }
            }
        }
    }

    @Override
    public IPage<T> page(IPage<T> page, Wrapper<T> queryWrapper) {
        String sqlStatement = SqlHelper.table(currentModelClass()).getSqlStatement("page");
        //说明在mapper.xml中实现了自定义page方法
        if (sqlSessionFactory.getConfiguration().hasStatement(sqlStatement)) {
            return this.baseMapper.selectPage(page, queryWrapper);
        }
        return super.page(page, queryWrapper);
    }


    @Override
    public String exportFullData(QueryWrapper queryWrapper, ExportParamDto exportParams, Map<String, Object> userParam, HttpServletRequest request) {
        List result = null;
        if (exportParams.getIsAll()) {
            result = this.list(queryWrapper);
        } else{
            IPage  page = QueryGenerator.parsePage(request);
            result = this.page(page,queryWrapper).getRecords();
        }

        try {
            String downloadPath = exportService.export(result, exportParams);
            return downloadPath;
        } catch (Exception e) {
            log.error("BaseServiceImpl.exportFullData", e);
            throw new BusinessException("文件生成失败，原因：" + e != null ? e.getMessage() : "未知异常");
        }
    }

    /**
     * @description:更新临时主键和相关附件
     * @param fileRefSuf 更新附件时，增加的后缀
     **/
    public void updateTempIdAndFileRef(DataEntity entity,String fileRefSuf) {
        String idTmp = entity.getId();
        fileRefSuf = UtilCommon.getStr(fileRefSuf, "");
        if (StringUtils.isNotBlank(idTmp) && idTmp.indexOf("temp_") != -1) {
            entity.setId(IdWorker.getIdStr());

            fileService.update(Wrappers.<File>update().eq("ref_id",idTmp + fileRefSuf).set("ref_id",entity.getId() + fileRefSuf));
        }
    }

    /**@Description： 导入模板字段标题
     */
    protected List<ExcelImportField> excelGetFieldHead() {
        List<ExcelImportField> exportHeadList = new ArrayList<ExcelImportField>();
        ExcelImportField headDTO;

        headDTO = new ExcelImportField();
        headDTO.setName("name1").setText("字段1").setCellIndex(0);
        exportHeadList.add(headDTO);

        headDTO = new ExcelImportField();
        headDTO.setName("name2").setText("字段2").setCellIndex(1);
        exportHeadList.add(headDTO);
        return  exportHeadList;
    }


    //下载导入模板
    @Override
    public void excelDownloadTemp(HttpServletResponse response, Map<String, String> reqData, List<ExcelImportField> exportHeadListParam) {
        response.setHeader("content-type", "application/octet-stream");
        response.setContentType("application/octet-stream");
        String sheetName = reqData.get("sheetName");
        String fileName = StrUtil.format("{}导入_{}.xls",sheetName, DateUtils.format(DateUtils.now(),DateUtils.YYYYMMDDHHMMSS_PATTER));
        try {
            response.setHeader("Content-Disposition", "attachment;filename=" + java.net.URLEncoder.encode(fileName, "UTF-8"));
        } catch (UnsupportedEncodingException e2) {
            e2.printStackTrace();
        }

        HSSFWorkbook workbook = new HSSFWorkbook();
        HSSFSheet sheet = workbook.createSheet(sheetName);
        int rowNextNum = 0;

        List<ExcelImportField> exportHeadList = exportHeadListParam;
        if (exportHeadList == null) {
            exportHeadList = excelGetFieldHead();
        }
        //表头列-一级
        ExcelImportField exportHeadDTO = null;
        HSSFRow row;
        HSSFCell cell;
        HSSFCellStyle cellStyle;
        HSSFFont font;
        HSSFRichTextString richTextString = null;
        HSSFCellStyle cellStyleTitleDefault;
        cellStyleTitleDefault = workbook.createCellStyle();
        //水平居中
        cellStyleTitleDefault.setAlignment(HorizontalAlignment.CENTER);

        font = workbook.createFont();
        //字体大小
        font.setFontHeightInPoints((short) 14);
        font.setBold(true);   //字体加粗
        cellStyleTitleDefault.setFont(font);
        cellStyleTitleDefault.setWrapText(true);
        cellStyleTitleDefault.setVerticalAlignment(VerticalAlignment.TOP); // 垂直顶部对齐
//        cellStyleTitleDefault.setLocked(true);

        HSSFCellStyle cellStyleTitleReq;
        HSSFFont fontReq;
        fontReq = workbook.createFont();
        //字体大小
        fontReq.setFontHeightInPoints((short) 14);
        fontReq.setBold(true);   //字体加粗
        fontReq.setColor(Font.COLOR_RED);
        cellStyleTitleReq = workbook.createCellStyle();
        //水平居中
        cellStyleTitleReq.setAlignment(HorizontalAlignment.CENTER);
        cellStyleTitleReq.setFont(fontReq);
        cellStyleTitleReq.setWrapText(true);
        cellStyleTitleReq.setVerticalAlignment(VerticalAlignment.TOP); // 垂直顶部对齐
//        cellStyleTitleReq.setLocked(true);


        //单元格锁定的样式
        HSSFCellStyle unlockstyle = workbook.createCellStyle();
        unlockstyle.setLocked(false);

        // 1. 固定第一行（冻结窗格）
        // 参数含义：冻结的列数，冻结的行数，右方可见的首列，下方可见的首行
        sheet.createFreezePane(0, 1, 0, 1);

        // 2. 开启过滤功能（作用于 A1:E11 区域）
        // 注意：lastRow 是包含表头的，所以数据有 10 行时，lastRow 应为 10
        sheet.setAutoFilter(CellRangeAddress.valueOf("A1:E11"));

        row = sheet.createRow(rowNextNum);
        int cellIndex = 0;
        String inputRemark ="";
        // 创建并配置小号字体
        Font smallFont = workbook.createFont();
        smallFont.setFontHeightInPoints((short) 8);  // 8 号字体

        String textAll ;
        for (int i = 0; i < exportHeadList.size(); i++) {
            exportHeadDTO = exportHeadList.get(i);
            cell = row.createCell(cellIndex);
            cellIndex++;
            inputRemark ="";
            if (StringUtils.isNotBlank(exportHeadDTO.getInputRemark())) {
                inputRemark = GlobalConsts.SpecialSymbol.WRAP+ exportHeadDTO.getInputRemark();
            }
            textAll = exportHeadDTO.getText() + inputRemark;
            richTextString = new HSSFRichTextString(exportHeadDTO.getText() + inputRemark);

            // 说明使用小写
            if (StringUtils.isNotBlank(exportHeadDTO.getInputRemark())) {
                richTextString.applyFont(exportHeadDTO.getText().length(), textAll.length(), smallFont); // 从第 16 个字符到第 23 个字符应用小号字体
            }

            cell.setCellValue(richTextString);

            if (exportHeadDTO.isRequired()) {
                cell.setCellStyle(cellStyleTitleReq);
            } else{
                cell.setCellStyle(cellStyleTitleDefault);
            }

            sheet.setDefaultColumnStyle(i, unlockstyle);
            //cell宽度
            sheet.setColumnWidth(i, (int) (UtilCommon.getStr(exportHeadDTO.getText(), "").getBytes().length * 1.2d * 256 > 12 * 256 ? UtilCommon.getStr(exportHeadDTO.getText(), "").getBytes().length * 1.2d * 256 : 12 * 256));
        }
        // 自动调整行高以适应多行文本
        row.setHeight((short)-1); // -1 表示自动调整


        OutputStream os1 = null;
        FileUtils.mkdirs(resPath + "download");
        String tmpFilePath = resPath + "download/"+fileName;
        java.io.File tmpFile = new java.io.File(tmpFilePath);
        byte[] buff = new byte[1024];
        BufferedInputStream bis = null;
        OutputStream os2 = null;

        try {
            os1 = new FileOutputStream(tmpFile);
            workbook.write(os1);

            os2 = response.getOutputStream();
            bis = new BufferedInputStream(new FileInputStream(tmpFile));
            int i = bis.read(buff);
            while (i != -1) {
                os2.write(buff, 0, buff.length);
                os2.flush();
                i = bis.read(buff);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }


    public List<?> excelGetDataList(MultipartFile file, Map<String, String> reqData,List<ExcelImportField> exportHeadList, Class<?> classTo) {
        FileUtils.mkdirs(resPath + "import");
        String sheetName = reqData.get("sheetName");
        String fileName = StrUtil.format("{}导入临时文件_{}.xls",sheetName,DateUtils.format(DateUtils.now(),DateUtils.YYYYMMDDHHMMSS_PATTER));
        String tmpFilePath = resPath + "import/"+fileName;
        java.io.File fileTmp = new java.io.File(tmpFilePath);
        try {
            BusinessException.throwErrorIf(!"XLS".equals(FileUtils.getFileSuffixWithoutDot(file).toUpperCase()), "请上传xls后缀的表格");
            if (!fileTmp.exists()) {
                fileTmp.createNewFile();
            }
            // 底层也是通过io流写入文件file
            FileCopyUtils.copy(file.getBytes(), fileTmp);
        }catch (Exception e) {
            throw new RuntimeException(e);
        }
        ArrayList<String[]> arrLst = null;//获取第一个sheet页
        try {
            arrLst = UtilExcelPoi.getListFromXls(fileTmp, 0);//获取第一个sheet页
        }catch (Exception e) {
            throw e;
        }

        List<String> arrHeadLst = Arrays.asList(arrLst.get(0));

        List<String> arrHeadLstCn = UtilCommon.getListOnlyCn(arrHeadLst);
        int findIndex = 0;
        for(ExcelImportField fieldHead  : exportHeadList) {
            findIndex = UtilCommon.getListIndex(arrHeadLstCn,fieldHead.getText());
            BusinessException.throwErrorIf(
                    findIndex != fieldHead.getCellIndex(),
                    "列[{}]位置错误，请查看excel模板，需要位置{}，实际位置{}",
                    fieldHead.getText(),
                    fieldHead.getCellIndex() + 1,
                    findIndex + 1
            );
        }

        String[] arrRow = null;
        T itemImport;
        List<T> itemImportList = new ArrayList<>();
        String value,valueTmp;
        Map<String,List<String>> mapValueList = new HashMap<>();
        boolean rowHasData = false;
        for (int nRow = 1; nRow < arrLst.size(); nRow++) {
            arrRow = arrLst.get(nRow);//Excel导数据的每行数据列
            try {
                if (classTo != null) {
                    itemImport = (T) classTo.newInstance();
                } else{
                    itemImport = currentModelClass().newInstance();
                }
            } catch (InstantiationException e) {
                throw new RuntimeException(e);
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            }

            // 该行都为空数据，则不处理
            rowHasData = false;
            for (int i = 0; i < arrRow.length; i++) {
                valueTmp = arrRow[i];
                if (!UtilCommon.strIsNull(valueTmp)) {
                    rowHasData = true;
                }
            }
            if (!rowHasData) {
                continue;
            }

            for(ExcelImportField head : exportHeadList) {
                value = StringUtils.trim(arrRow[head.getCellIndex()]);
                if (head.isRequired() && StringUtils.isBlank(value)) {
                    BusinessException.throwError("第[{}]行，[{}]必填，请检查",nRow +1 +"",head.getText());
                }
                if (
                        head.isUnq()
                                && StringUtils.isNotBlank(value)
                                && mapValueList.containsKey(head.getName())
                                && mapValueList.get(head.getName()).contains(value)
                ) {
                    BusinessException.throwError("第[{}]行，[{}]重复出现，请检查",nRow +1 +"",head.getText());
                }

                if (!mapValueList.containsKey(head.getName())) {
                    mapValueList.put(head.getName(), new ArrayList<>());
                }
                mapValueList.get(head.getName()).add(value);

                if (StringUtils.isNotBlank(value)) {
                    if (head.isNumber() && !NumberUtils.isNumber(value)) {
                        BusinessException.throwError("第[{}]行，[{}]格式应为数字，请检查",nRow +1 +"",head.getText());
                    }
                    if (head.isDate() && !DateUtils.isValidDate(value)) {
                        // 自定义日期格式无法识别，此处增加个转换
                        try {
                            value = DateUtils.excelDaysToDate(Integer.valueOf(UtilNum.trimZero(value)));
                        }catch (Exception e2) {
                            Date dateNew = DateUtils.parse(value);
                            if (dateNew == null) {
                                BusinessException.throwError("第[{}]行，[{}]格式应为日期，请检查",nRow +1 +"",head.getText());
                            } else{
                                value =DateUtils.format(dateNew,DateUtils.DATE_PATTERN);
                            }
                        }

                        if (!DateUtils.isValidDate(value)) {
                            BusinessException.throwError("第[{}]行，[{}]格式应为日期，请检查",nRow +1 +"",head.getText());
                        }
                    }
                    if (head.getMaxLength() != null && UtilCommon.length(value) > head.getMaxLength()) {
                        BusinessException.throwError("第[{}]行，[{}]长度已超过最大值[{}]，请检查",nRow +1 +"",head.getText(), head.getMaxLength());
                    }
                    if (head.getRequiredLength() != null && head.getRequiredLength() > 0 && UtilCommon.length(value) != head.getRequiredLength()) {
                        BusinessException.throwError("第[{}]行，[{}]长度应该为[{}]，请检查",nRow +1 +"",head.getText(), head.getRequiredLength());
                    }
                    if (head.getCheckValueList() != null && !head.getCheckValueList().contains(value)) {
                        BusinessException.throwError("第[{}]行，[{}]格式错误，可选[{}]，请检查",nRow +1 +"",head.getText(), StringUtils.join(head.getCheckValueList(), "或者"));
                    }
                    BeanUtil.setFieldValue(itemImport,head.getName(),value);
                }
            }
            itemImportList.add(itemImport);
        }
        if (itemImportList.isEmpty()) {
            BusinessException.throwError("没有可导入的数据");
        }
        return  itemImportList;
    }
}
